package client

import (
	"context"
	"encoding/json"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"io/ioutil"
	"strconv"
	"sync"
	"time"
)

// Container Container
type Container struct {
}

func newContainer() *Container {
	return &Container{}
}

// Create 创建容器
func (c *Container) Create(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
	return ObtainClient().ContainerCreate(ctx, config, hostConfig, networkingConfig, containerName)
}

// Start 启动容器
func (c *Container) Start(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
	return ObtainClient().ContainerStart(ctx, containerID, options)
}

// Restart 重启容器
func (c *Container) Restart(ctx context.Context, containerID string, timeout *time.Duration) error {
	return ObtainClient().ContainerRestart(ctx, containerID, timeout)
}

// Stop 停止运行中的容器
//
// ContainerStop停止容器。如果容器未能在超时参数指定的时间范围内优雅停止，则会强制终止(杀死)它。
//
// 如果超时为nil，则使用容器的StopTimeout值，如果设置了该值，则使用引擎的默认值。可以指定负超时值，这意味着没有超时，即不执行强制终止。
func (c *Container) Stop(ctx context.Context, containerID string, timeout *time.Duration) error {
	if nil == timeout {
		defaultTimeout := 5 * time.Second
		return ObtainClient().ContainerStop(ctx, containerID, &defaultTimeout)
	}
	return ObtainClient().ContainerStop(ctx, containerID, timeout)
}

// Remove 终止并从docker主机中移除一个容器
func (c *Container) Remove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error {
	return ObtainClient().ContainerRemove(ctx, containerID, options)
}

// Logs 获取容器日志
func (c *Container) Logs(ctx context.Context, containerID string, since, until time.Time) ([]byte, error) {
	body, err := ObtainClient().ContainerLogs(ctx, containerID, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Timestamps: false,
		Details:    false,
		Follow:     false,
		Since:      strconv.FormatInt(since.Unix(), 10), // "1589772560.000000001"
		Until:      strconv.FormatInt(until.Unix(), 10), // "1589772720.000000001"
	})
	if nil != err {
		return nil, err
	}
	defer func() { _ = body.Close() }()
	return ioutil.ReadAll(body)
}

// List 获取服务信息
func (c *Container) List(ctx context.Context) ([]types.Container, error) {
	return ObtainClient().ContainerList(ctx, types.ContainerListOptions{})
}

// Inspect 获取容器信息
func (c *Container) Inspect(ctx context.Context, containerID string) (types.ContainerJSON, []byte, error) {
	return ObtainClient().ContainerInspectWithRaw(ctx, containerID, true)
}

// Stats Stats
func (c *Container) Stats(ctx context.Context, containerID string) (*types.Stats, string, error) {
	containerStats, err := ObtainClient().ContainerStats(ctx, containerID, true)
	if nil != err {
		return nil, "", err
	}

	dec := json.NewDecoder(containerStats.Body)
	defer func() { _ = containerStats.Body.Close() }()
	var s *types.Stats
	if err = dec.Decode(&s); nil != err {
		return nil, "", err
	}
	return s, containerStats.OSType, err
}

// StatsAll StatsAll
func (c *Container) StatsAll(ctx context.Context) ([]*types.Stats, error) {
	containers, err := ObtainClient().ContainerList(ctx, types.ContainerListOptions{})
	if nil != err {
		return nil, err
	}
	var (
		stats []*types.Stats
		wg    sync.WaitGroup
	)
	for _, container := range containers {
		wg.Add(1)
		go func() {
			stat, _, errNew := c.Stats(ctx, container.ID)
			if nil != errNew {
				err = errNew
			} else {
				stats = append(stats, stat)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	if nil != err {
		return nil, err
	}
	return stats, nil
}
